/*
                    Map Class Definition
*/
#include "Map.h"

Map::Map()
{
    iWidth = 0;
    iHeight = 0;
    iTilesWidth = 0;
    iTilesHeight = 0;

    strName = "";

    iActiveCollisionLayer = 0;
    iTotalTiles = 0;
}

Map::~Map()
{
    for (int i = 0; i < Tilelayers.size(); i++)
    {
        delete Tilelayers.at(i);
    }

    Tilelayers.clear();

    for (int i = 0; i < Collisionlayers.size(); i++)
    {
        delete Collisionlayers.at(i);
    }

    Collisionlayers.clear();

    for (int i = 0; i < DrawingLayers.size(); i++)
    {
        SDL_FreeSurface(DrawingLayers.at(i));
        DrawingLayers.at(i) = NULL;
    }

    DrawingLayers.clear();

    for (int i = 0; i < Eventlayers.size(); i++)
    {
        delete Eventlayers.at(i);
    }

    Eventlayers.clear();
}

int Map::getWidth()
{
    return iWidth;
}

int Map::getHeight()
{
    return iHeight;
}

int Map::getTilesWidth()
{
    return iTilesWidth;
}

int Map::getTilesHeight()
{
    return iTilesHeight;
}

void Map::setName(const string& strNewName)
{
    strName = strNewName;
}

string Map::getName()
{
    return strName;
}

void Map::setTilesheet(const string& strNewTilesheet)
{
    strTilesheet = strNewTilesheet;
}

string Map::getTilesheet()
{
    return strTilesheet;
}

int Map::BlitToSurface(SDL_Surface *surface, const SDL_Rect& view)
{
    SDL_Rect dstRect, srcRect;

    dstRect.x = view.x;
    dstRect.y = view.y;

    srcRect = view;

    for (int i = 0; i < DrawingLayers.size(); i++)
    {
        SDL_BlitSurface(DrawingLayers.at(i), &srcRect, surface, &dstRect);
    }

    return 0;
}

int Map::BlitLayerToSurface(SDL_Surface *surface, const int& iLayer, const SDL_Rect& view)
{
    SDL_Rect dstRect, srcRect;

    dstRect.x = view.x;
    dstRect.y = view.y;

    srcRect = view;

    if ((DrawingLayers.size() > iLayer) && (iLayer >= 0))
    {
        if (SDL_BlitSurface(DrawingLayers.at(iLayer), &srcRect, surface, &dstRect) == -1)
        {
            return -1;
        }

        return 0;
    }
    else
    {
        return -1;
    }
}

int Map::BlitFromLayerToSurface(SDL_Surface *surface, const int& iLayer, const SDL_Rect& view)
{
    SDL_Rect dstRect, srcRect;

    dstRect.x = view.x;
    dstRect.y = view.y;

    srcRect = view;

    if ((Tilelayers.size() > iLayer) && (iLayer >= 0))
    {
        for (int i = iLayer; i < DrawingLayers.size(); i++)
        {
            SDL_BlitSurface(DrawingLayers.at(i), &srcRect, surface, &dstRect);
        }

        return 0;
    }
    else
    {
        return -1;
    }
}

int Map::AddEventLayer()
{
    Eventlayer *tempEventLayer = new Eventlayer;

    Event* tempEvent = NULL;

    for (int hc = 0; hc < iHeight; hc += TILE_HEIGHT)
    {
        for (int wc = 0; wc < iWidth; wc += TILE_WIDTH)
        {
            tempEvent = new Event;

            tempEvent->setPosition(Point(wc, hc));
            tempEvent->setEventType(EVENTTYPE_NONE);

            tempEventLayer->Events.push_back(tempEvent);
        }
    }

    Eventlayers.push_back(tempEventLayer);

    tempEventLayer = NULL;
    tempEvent = NULL;

    return 0;
}

Event* Map::GetEventAtPosition(Point Position)
{
    int i = GetTileByPosition(Position);

    if (i < 0 || i >= Eventlayers.at(0)->Events.size())
    {
        return NULL;
    }
    else
    {
        return Eventlayers.at(0)->Events.at(i);
    }
}

int Map::ImportFromXML(string strFilename)
{
    if (populate(strFilename) == -1)
    {
        printf("XML populate failed on map %s\n", strFilename.c_str());
        return -1;
    }

    strName = name();

    strTilesheet = tilesheetname();

    iWidth = convertTo<int>(width());
    iHeight = convertTo<int>(height());

    iTilesWidth = iWidth / TILE_WIDTH;
    iTilesHeight = iHeight / TILE_HEIGHT;

    iTotalTiles = iTilesWidth * iTilesHeight;

    for (int i = 0; i < Tilelayers.size(); i++)
    {
        delete Tilelayers.at(i);
    }
    Tilelayers.clear();

    parseTileLayer();

    for (int i = 0; i < Collisionlayers.size(); i++)
    {
        delete Collisionlayers.at(i);
    }
    Collisionlayers.clear();

    parseCollisionLayer();

    for (int i = 0; i < Eventlayers.size(); i++)
    {
        delete Eventlayers.at(i);
    }
    Eventlayers.clear();

    parseEventLayer();

    GenerateDrawingLayers();

    xmlXPathFreeContext(xpathRoot);

    xmlFreeDoc(doc);

    return 0;
}

void Map::GenerateDrawingLayers()
{
    // Clear any layers that existed before (technically will never be needed)
    for (int i = 0; i < DrawingLayers.size(); i++)
    {
        SDL_FreeSurface(DrawingLayers.at(i));
        DrawingLayers.at(i) = NULL;
    }

    DrawingLayers.clear();
    //*****************************************************

    int iColourKey = 0x00FF00;

    if (Tilesheets::SheetLoaded(strTilesheet) == false)
    {
        Tilesheets::Freesheets();
        Tilesheets::Loadsheet(strTilesheet);
    }

    // Create 2 layers for above and below the player
    for (int i = 0; i < 2; i++)
    {
        SDL_Surface *temp;

        temp = SDL_CreateRGBSurface(SDL_HWSURFACE, iWidth, iHeight, 32, RMASK, GMASK, BMASK, 0);

        if (temp != NULL)
        {
            DrawingLayers.push_back(SDL_DisplayFormat(temp));

            SDL_FreeSurface(temp);

            temp = NULL;

            SDL_FillRect(DrawingLayers.back(), NULL, iColourKey);

            SDL_SetColorKey(DrawingLayers.back(), SDL_SRCCOLORKEY | SDL_RLEACCEL, iColourKey);
        }
    }

    if (DrawingLayers.size() == 2)
    {
        for (int i = 0; i < Tilelayers.size(); i++)
        {
            for (int t = 0; t < Tilelayers.at(i)->Tiles.size(); t++)
            {
                Tilesheets::BlitTile(strTilesheet, Tilelayers.at(i)->Tiles.at(t)->TilesheetPosition, Tilelayers.at(i)->Tiles.at(t)->Position, (i < 3) ? DrawingLayers.at(0) : DrawingLayers.at(1));
            }
        }
    }
}

// IMPORT MECHANICS*****************************************************************************************************

int Map::populate(string filename)
{
    xmlXPathObjectPtr result;

    doc = xmlParseFile(filename.c_str());

    if(doc == NULL)
    {
        return -1;
    }
    else
    {
        return 0;
    }
}

string Map::name()
{
    xmlXPathObjectPtr result;

    xpathRoot = xmlXPathNewContext(doc);
    result = xmlXPathEvalExpression((xmlChar*)"/map/name/text()", xpathRoot);
    if(!xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        string strTemp = (char*)result->nodesetval->nodeTab[0]->content;

        xmlXPathFreeObject(result);

        return strTemp;
    }
}

string Map::tilesheetname()
{
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/tilesheetname/text()", xpathRoot);
    if(!xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        string strTemp = (char*)result->nodesetval->nodeTab[0]->content;

        xmlXPathFreeObject(result);

        return strTemp;
    }
}

string Map::width()
{
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/width/text()", xpathRoot);
    if(!xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        string strTemp = (char*)result->nodesetval->nodeTab[0]->content;

        xmlXPathFreeObject(result);

        return strTemp;
    }
}

string Map::height()
{
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/height/text()", xpathRoot);
    if(!xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        string strTemp = (char*)result->nodesetval->nodeTab[0]->content;

        xmlXPathFreeObject(result);

        return strTemp;
    }
}

void Map::parseTileLayer()
{
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/tilelayer", xpathRoot);

    if(xmlXPathNodeSetIsEmpty(result->nodesetval))
    {

    }
        xmlNodePtr cur = NULL;

    for (int i = 0; i < result->nodesetval->nodeNr; i++)
    {
        Tilelayers.push_back(new Tilelayer);

        cur = result->nodesetval->nodeTab[i]->xmlChildrenNode;

        while(cur != NULL)
        {
            if(!xmlStrcmp(cur->name, (const xmlChar *)"tilemap"))
            {
                parseTileMap(doc, cur);
            }
            cur=cur->next;
        }
    }

    cur = NULL;

    xmlXPathFreeObject(result);
}

void Map::parseTileMap(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur=cur->xmlChildrenNode;
    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"drawingorder"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            xmlFree(key);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"tile"))
        {
            Tilelayers.back()->Tiles.push_back(new Tile());
            parseTile(doc, cur);
        }
        cur=cur->next;
    }
}

void Map::parseTile(xmlDocPtr doc, xmlNodePtr cur)
{
    cur=cur->xmlChildrenNode;
    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"sheetposition"))
        {
            parseSheetPosition(doc, cur);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"mapposition"))
        {
            parseMapPosition(doc, cur);
        }
        cur=cur->next;
    }
}

void Map::parseSheetPosition(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur=cur->xmlChildrenNode;
    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"x"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            Tilelayers.back()->Tiles.back()->TilesheetPosition.x = lexical_cast<int>(key);
            xmlFree(key);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"y"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            Tilelayers.back()->Tiles.back()->TilesheetPosition.y = lexical_cast<int>(key);
            xmlFree(key);
        }
        cur=cur->next;
    }
}

void Map::parseMapPosition(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur=cur->xmlChildrenNode;
    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"x"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            Tilelayers.back()->Tiles.back()->Position.x = lexical_cast<int>(key);
            xmlFree(key);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"y"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            Tilelayers.back()->Tiles.back()->Position.y = lexical_cast<int>(key);
            xmlFree(key);
        }
        cur=cur->next;
    }
}

void Map::parseCollisionLayer()
{
    xmlChar *key;
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/collision", xpathRoot);

    if(xmlXPathNodeSetIsEmpty(result->nodesetval))
    {

    }
        xmlNodePtr cur = NULL;

    for (int i = 0; i < result->nodesetval->nodeNr; i++)
    {
        Collisionlayers.push_back(new Collisionlayer);

        cur = result->nodesetval->nodeTab[i];

        key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);

        string strTemp = (char*)key;

        xmlFree(key);

        parseCollisionmap(strTemp);
    }

    cur = NULL;

    xmlXPathFreeObject(result);
}

void Map::parseCollisionmap(string strMap)
{
    double iCurX = (double)0;
    double iCurY = (double)0;

    CollisionMatrix.resize(iWidth / TILE_WIDTH, iHeight / TILE_HEIGHT);
    FillMatrix(CollisionMatrix, 0);

    // i has to start at 1 to ignore the \n at the start of the string
    for (int i = 1; i < strMap.length(); i++)
    {
        if (strMap.at(i) != ',')
        {
            Collisionlayers.back()->Collisions.push_back(new Collision());
            Collisionlayers.back()->Collisions.back()->Position.x = iCurX;
            Collisionlayers.back()->Collisions.back()->Position.y = iCurY;

            int iType = lexical_cast<int>(strMap.at(i));

            if (iType != COLLISIONTYPE_OPEN)
            {
                CollisionMatrix((int)iCurX / TILE_WIDTH, (int)iCurY / TILE_HEIGHT) = 1;
            }

            Collisionlayers.back()->Collisions.back()->iCollisionType = iType;

            iCurX += (double)TILE_WIDTH;

            if (iCurX >= iWidth)
            {
                iCurY += (double)TILE_HEIGHT;
                iCurX = (double)0;
            }
        }
    }
}

boost::numeric::ublas::matrix<int> Map::getCollisionMatrix()
{
    return CollisionMatrix;
}

void Map::parseEventLayer()
{
    xmlXPathObjectPtr result;

    result = xmlXPathEvalExpression((xmlChar*)"/map/eventlayer", xpathRoot);

    if(xmlXPathNodeSetIsEmpty(result->nodesetval))
    {

    }
        xmlNodePtr cur;

    for (int i = 0; i < result->nodesetval->nodeNr; i++)
    {
        AddEventLayer();

        cur = result->nodesetval->nodeTab[i]->xmlChildrenNode;

        while(cur != NULL)
        {
            if(!xmlStrcmp(cur->name, (const xmlChar *)"event"))
            {
                parseEvent(doc, cur);
            }
            cur=cur->next;
        }
    }
}

void Map::parseEvent(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur=cur->xmlChildrenNode;

    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"position"))
        {
            parseEventPosition(doc, cur);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"action"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);

            string strTemp = "";

            if (key != NULL) { strTemp = (char*)key; }

            if (iCurEvent >= 0 && iCurEvent < Eventlayers.back()->Events.size())
            {
                Eventlayers.back()->Events.at(iCurEvent)->setValue(strTemp);
            }

            xmlFree(key);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"type"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);

            if (iCurEvent >= 0 && iCurEvent < Eventlayers.back()->Events.size())
            {
                Eventlayers.back()->Events.at(iCurEvent)->setEventType(lexical_cast<int>(key));
            }

            xmlFree(key);
        }
        cur=cur->next;
    }
}

void Map::parseEventPosition(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur=cur->xmlChildrenNode;

    while(cur != NULL)
    {
        if(!xmlStrcmp(cur->name, (const xmlChar *)"x"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            tempmp.x = lexical_cast<int>(key);
            xmlFree(key);
        }
        if(!xmlStrcmp(cur->name, (const xmlChar *)"y"))
        {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            tempmp.y = lexical_cast<int>(key);
            xmlFree(key);
        }
        cur=cur->next;
    }

    iCurEvent = GetTileByPosition(tempmp);

    if (iCurEvent >= 0 && iCurEvent < Eventlayers.back()->Events.size())
    {
        Eventlayers.back()->Events.at(iCurEvent)->setPosition(tempmp);
    }
}

Point Map::GetPlayerNextPosition(Point Position, int iDirection)
{
    Point Destination = Position;

    switch (iDirection)
    {
        case UP:
            Destination.y -= DISTANCE_MOVE;
        break;

        case DOWN:
            Destination.y += DISTANCE_MOVE;
        break;

        case LEFT:
            Destination.x -= DISTANCE_MOVE;
        break;

        case RIGHT:
            Destination.x += DISTANCE_MOVE;
        break;
    }

    if (Destination.x < 0)
    {
        Destination.x = iWidth - DISTANCE_MOVE;
    }
    else if (Destination.y < 0)
    {

        Destination.y = iHeight - DISTANCE_MOVE;
    }
    else if (Destination.x >= iWidth)
    {
        Destination.x = 0;
    }
    else if (Destination.y >= iHeight)
    {
        Destination.y = 0;
    }

    return Destination;
}

int Map::GetPlayerNextCollision(Point Position, int iDirection)
{
    Point Destination = GetPlayerNextPosition(Position, iDirection);

    int iTile = GetTileByPosition(Destination);

    if (iTile == -1)
    {
        return -1;
    }

    return Collisionlayers.at(iActiveCollisionLayer)->Collisions.at(iTile)->iCollisionType;
}

int Map::GetCollisionAtPoint(Point Position)
{
    int iTile = GetTileByPosition(Position);

    if (iTile == -1)
    {
        return -1;
    }

    return Collisionlayers.at(iActiveCollisionLayer)->Collisions.at(iTile)->iCollisionType;
}

Point Map::FindSpawnPosition()
{
    int iX = rand() % (iWidth / TILE_WIDTH);
    int iY = rand() % (iHeight / TILE_HEIGHT);

    iX *= TILE_WIDTH;
    iY *= TILE_HEIGHT;

    Point Position(iX, iY);

    if (GetCollisionAtPoint(Position) != COLLISIONTYPE_OPEN)
    {
        return FindSpawnPosition();
    }

    return Position;
}

int Map::GetTileByPosition(Point Position)
{
    int iTilesAcross = iWidth / TILE_WIDTH;
    int iTilesHigh = iHeight / TILE_HEIGHT;

    if ((Position.x >= iWidth) || (Position.y >= iHeight))
    {
       // tile does not exist
       return -1;
    }

    if ((Position.x < 0) || (Position.y < 0))
    {
        // tile does not exist
        return -1;
    }

    int iPositionAcross = static_cast<int>(Position.x / TILE_WIDTH);
    int iPositionHigh = static_cast<int>(Position.y / TILE_HEIGHT);

    int iTileIndex = (iPositionHigh * iTilesAcross) + iPositionAcross;

    if (iTileIndex < 0)
    {
         // tile does not exist
         return -1;
    }
    else if (iTileIndex >= iTotalTiles)
    {
         // tile does not exist
         return -1;
    }
    else
    {
        return iTileIndex;
    }
}

// *********************************************************************************************************************
